// import com.alibaba.druid.DbType;
// import com.alibaba.druid.sql.SQLUtils;
// import com.alibaba.druid.sql.ast.SQLDataType;
// import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
// import com.alibaba.druid.sql.ast.SQLExprImpl;
// import com.alibaba.druid.sql.visitor.SQLASTVisitor;
// import com.alibaba.druid.util.MySqlUtils;

// import java.text.ParseException;
// import java.text.SimpleDateFormat;
// import java.util.*;

#include "SQLTimestampExpr.h"
#include "../SQLDataTypeImpl.h"
#include "../../../utils/MySqlUtils.h"
#include "../../visitor/SQLASTVisitor.h"
#include "../../SQLUtils.h"
#include <sstream>
#include <ctime>
#include <iostream>
#include <iomanip>

SQLDataType_ptr SQLTimestampExpr::DATA_TYPE = SQLDataType_ptr(new SQLDataTypeImpl(make_string_ptr("timestamp")));

SQLTimestampExpr::SQLTimestampExpr()
{
}

SQLTimestampExpr::SQLTimestampExpr(string_ptr literal)
{
  this->literal = literal;
}

SQLTimestampExpr::SQLTimestampExpr(std::chrono::system_clock::time_point date)
{
  setLiteral(date);
}

SQLTimestampExpr::SQLTimestampExpr(std::chrono::system_clock::time_point date, string_ptr timeZone)
{
  setLiteral(date, timeZone);
}

SQLObject_ptr SQLTimestampExpr::clone()
{
  SQLObject_ptr clone_tmp = std::shared_ptr<SQLTimestampExpr>(new SQLTimestampExpr());
  std::shared_ptr<SQLTimestampExpr> x = std::dynamic_pointer_cast<SQLTimestampExpr>(clone_tmp);
  // SQLTimestampExpr x = new SQLTimestampExpr();
  x->literal = literal;
  x->timeZone = timeZone;
  x->withTimeZone = withTimeZone;
  return x;
}

std::shared_ptr<std::chrono::system_clock::time_point> SQLTimestampExpr::getDate(string_ptr timeZone)
{
  if (literal->empty() || literal->length() == 0)
  {
    return nullptr;
  }

  return MySqlUtils::parseDate(literal, timeZone);
}

BOOL_ptr SQLTimestampExpr::addDay(int delta)
{
  if (literal->empty())
  {
    return BOOL::FALSE;
  }
  std::tm tm = std::tm{};
  std::stringstream ss(*literal);
  ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

  if (ss.fail())
  {
    std::cout << "Failed to parse time." << std::endl;
    return BOOL::FALSE;
  }

  std::chrono::system_clock::time_point tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
  tp = tp + std::chrono::hours(24 * delta);
  std::time_t now_c = std::chrono::system_clock::to_time_t(tp);
  std::stringstream sstream;
  std::string text;
  sstream << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
  sstream >> text;
  setLiteral(make_string_ptr(text));

  // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  // try {
  // boost::posix_time::ptime calendar = boost::posix_time::time_from_string(literal); // format.parse(literal);
  // Calendar calendar = Calendar.getInstance();
  // calendar.setTime(date);
  // calendar.add(Calendar.DAY_OF_MONTH, delta);
  // calendar = calendar + boost::gregorian::days(delta);

  // string_ptr  result_chars = format.format(calendar.getTime());
  // string_ptr  result_chars = boost::posix_time::to_simple_string(calendar);
  // setLiteral(result_chars);
  // return BOOL::TRUE;
  // } catch (ParseException e) {
  //     // skip
  // }

  // return BOOL::FALSE;
}

BOOL_ptr SQLTimestampExpr::addMonth(int delta)
{
  if (literal->empty())
  {
    return BOOL::FALSE;
  }
  //     std::tm tm = std::tm{};
  //   std::stringstream ss(literal);
  //   ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

  //   if (ss.fail())
  //   {
  //     std::cout << "Failed to parse time." << std::endl;
  //     return BOOL::FALSE;
  //   }

  //   std::chrono::system_clock::time_point tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
  // tp = tp + std::chrono::months(24 * delta);
  //   std::time_t now_c = std::chrono::system_clock::to_time_t(tp);
  //   std::stringstream sstream;
  //   string_ptr  text;
  //   sstream << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
  //   sstream >> text;
  //   setLiteral(text);

  // boost::posix_time::ptime calendar = boost::posix_time::time_from_string(literal);
  // calendar = calendar + boost::gregorian::months(delta);
  // string_ptr  result_chars = boost::posix_time::to_simple_string(calendar);
  // setLiteral(result_chars);
  // return BOOL::TRUE;

  // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  // try {
  //     boost::posix_time::ptime date = format.parse(literal);
  //     Calendar calendar = Calendar.getInstance();
  //     calendar.setTime(date);
  //     calendar.add(Calendar.MONTH, delta);
  //     string_ptr  result_chars = format.format(calendar.getTime());
  //     setLiteral(result_chars);
  //     return BOOL::TRUE;
  // } catch (ParseException e) {
  //     // skip
  // }

  // return BOOL::FALSE;
}

BOOL_ptr SQLTimestampExpr::addHour(int delta)
{
  if (literal->empty())
  {
    return BOOL::FALSE;
  }

  std::tm tm = std::tm{};
  std::stringstream ss(*literal);
  ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

  if (ss.fail())
  {
    std::cout << "Failed to parse time." << std::endl;
    return BOOL::FALSE;
  }

  std::chrono::system_clock::time_point tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
  tp = tp + std::chrono::hours(delta);
  std::time_t now_c = std::chrono::system_clock::to_time_t(tp);
  std::stringstream sstream;
  std::string text;
  sstream << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
  sstream >> text;
  setLiteral(make_string_ptr(text));

  // boost::posix_time::ptime calendar = boost::posix_time::time_from_string(literal);
  // calendar = calendar + boost::posix_time::hours(delta);
  // string_ptr  result_chars = boost::posix_time::to_simple_string(calendar);
  // setLiteral(result_chars);
  // return BOOL::TRUE;
  // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  // try {
  //     boost::posix_time::ptime date = format.parse(literal);
  //     Calendar calendar = Calendar.getInstance();
  //     calendar.setTime(date);
  //     calendar.add(Calendar.HOUR_OF_DAY, delta);
  //     string_ptr  result_chars = format.format(calendar.getTime());
  //     setLiteral(result_chars);
  //     return BOOL::TRUE;
  // } catch (ParseException e) {
  //     // skip
  // }

  // return BOOL::FALSE;
}

BOOL_ptr SQLTimestampExpr::addMiniute(int delta)
{
  if (literal->empty())
  {
    return BOOL::FALSE;
  }

  std::tm tm = std::tm{};
  std::stringstream ss(*literal);
  ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");

  if (ss.fail())
  {
    std::cout << "Failed to parse time." << std::endl;
    return BOOL::FALSE;
  }

  std::chrono::system_clock::time_point tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
  tp = tp + std::chrono::minutes(delta);
  std::time_t now_c = std::chrono::system_clock::to_time_t(tp);
  std::stringstream sstream;
  std::string text;
  sstream << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
  sstream >> text;
  setLiteral(make_string_ptr(text));

  // boost::posix_time::ptime calendar = boost::posix_time::time_from_string(literal);
  // calendar = calendar + boost::posix_time::minutes(delta);
  // string_ptr  result_chars = boost::posix_time::to_simple_string(calendar);
  // setLiteral(result_chars);
  // return BOOL::TRUE;

  // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  // try {
  //     boost::posix_time::ptime date = format.parse(literal);
  //     Calendar calendar = Calendar.getInstance();
  //     calendar.setTime(date);
  //     calendar.add(Calendar.MINUTE, delta);
  //     string_ptr  result_chars = format.format(calendar.getTime());
  //     setLiteral(result_chars);
  //     return BOOL::TRUE;
  // } catch (ParseException e) {
  //     // skip
  // }

  // return BOOL::FALSE;
}

string_ptr SQLTimestampExpr::getValue()
{
  return literal;
}

string_ptr SQLTimestampExpr::getLiteral()
{
  return literal;
}

void SQLTimestampExpr::setLiteral(string_ptr literal)
{
  this->literal = literal;
}

void SQLTimestampExpr::setLiteral(std::chrono::system_clock::time_point x)
{
  setLiteral(x, nullptr);
}

void SQLTimestampExpr::setLiteral(std::chrono::system_clock::time_point x, string_ptr timeZone)
{
  // if (x == nullptr) {
  //     this->literal = nullptr;
  //     return;
  // }

  // SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  // if (timeZone != nullptr) {
  //     format.setTimeZone(timeZone);
  // }
  // this->literal = boost::posix_time::to_simple_string(x);

  std::time_t now_c = std::chrono::system_clock::to_time_t(x);
  std::stringstream sstream;
  std::string text;
  sstream << std::put_time(std::localtime(&now_c), "%Y-%m-%d %H:%M:%S");
  sstream >> text;
  this->literal = make_string_ptr(text);
}

string_ptr SQLTimestampExpr::getTimeZone()
{
  return this->timeZone;
}

void SQLTimestampExpr::setTimeZone(string_ptr timeZone)
{
  this->timeZone = timeZone;
}

BOOL_ptr SQLTimestampExpr::isWithTimeZone()
{
  return withTimeZone;
}

void SQLTimestampExpr::setWithTimeZone(BOOL_ptr withTimeZone)
{
  this->withTimeZone = withTimeZone;
}

//@Override
int SQLTimestampExpr::hashCode()
{
  int prime = 31;
  int result = 1;
  result = prime * result + ((literal->empty()) ? 0 : literal->length());
  result = prime * result + ((timeZone->empty()) ? 0 : timeZone->length());
  result = prime * result + (withTimeZone ? 1231 : 1237);
  return result;
}

//@Override
bool SQLTimestampExpr::equals(Object_ptr obj)
{
  if (this == obj.get())
  {
    return true;
  }
  if (obj == nullptr)
  {
    return false;
  }
  if (getClass() != obj->getClass())
  {
    return false;
  }
  std::shared_ptr<SQLTimestampExpr> other = std::dynamic_pointer_cast<SQLTimestampExpr>(obj);
  if (literal->empty())
  {
    if (!other->literal->empty())
    {
      return false;
    }
  }
  else if (!(literal == other->literal))
  {
    return false;
  }
  if (timeZone->empty())
  {
    if (!other->timeZone->empty())
    {
      return false;
    }
  }
  else if (!(timeZone == other->timeZone))
  {
    return false;
  }
  if (withTimeZone != other->withTimeZone)
  {
    return false;
  }
  return true;
}

//@Override
void SQLTimestampExpr::accept0(SQLASTVisitor_ptr visitor)
{
  visitor->visit(SharedObject(SQLTimestampExpr));

  visitor->endVisit(SharedObject(SQLTimestampExpr));
}

string_ptr SQLTimestampExpr::toString()
{
  return (SQLUtils::toSQLString(std::dynamic_pointer_cast<SQLObject>(SharedObject(SQLTimestampExpr)), (DbType_ptr) nullptr));
}

SQLDataType_ptr SQLTimestampExpr::computeDataType()
{
  return DATA_TYPE;
}

//@Override
SQLObject_list_ptr SQLTimestampExpr::getChildren()
{
  return SQLObject_list_ptr();
}

BOOL_ptr SQLTimestampExpr::check(string_ptr str)
{
  int len;
  if (str->empty() || (len = str->length()) < 14 || len > 23)
  {
    return BOOL::FALSE;
  }

  char c0 = str->at(0);
  char c1 = str->at(1);
  char c2 = str->at(2);
  char c3 = str->at(3);
  char c4 = str->at(4);
  char c5 = str->at(5);
  char c6 = str->at(6);
  char c7 = str->at(7);
  char c8 = str->at(8);
  char c9 = str->at(9);
  char c10 = str->at(10);

  // check year
  if (c0 < '1' | c0 > '9')
  {
    return BOOL::FALSE;
  }
  if (c1 < '0' | c1 > '9')
  {
    return BOOL::FALSE;
  }
  if (c2 < '0' | c2 > '9')
  {
    return BOOL::FALSE;
  }
  if (c3 < '0' | c3 > '9')
  {
    return BOOL::FALSE;
  }

  int year = (c0 - '0') * 1000 + (c1 - '0') * 100 + (c2 - '0') * 10 + (c3 - '0');
  if (year < 1000)
  {
    return BOOL::FALSE;
  }

  if (c4 != '-')
  {
    return BOOL::FALSE;
  }

  int month, day;
  char M0 = 0, M1 = 0, d0 = 0, d1 = 0;
  if (c8 == ' ')
  {
    if (c6 != '-')
    {
      return BOOL::FALSE;
    }

    M1 = c5;
    d1 = c7;
  }
  else if (c9 == ' ')
  {
    if (c6 == '-')
    {
      M1 = c5;
      d0 = c7;
      d1 = c8;
    }
    else if (c7 == '-')
    {
      M0 = c5;
      M1 = c6;
      d1 = c8;
    }
    else
    {
      return BOOL::FALSE;
    }
  }
  else if (c10 == ' ')
  {
    if (c7 != '-')
    {
      return BOOL::FALSE;
    }

    M0 = c5;
    M1 = c6;
    d0 = c8;
    d1 = c9;
  }
  else
  {
    return BOOL::FALSE;
  }

  if (M0 == 0)
  {
    if (M1 < '0' || M1 > '9')
    {
      return BOOL::FALSE;
    }
    month = M1 - '0';
  }
  else
  {
    if (M0 != '0' && M0 != '1')
    {
      return BOOL::FALSE;
    }
    if (M1 < '0' || M1 > '9')
    {
      return BOOL::FALSE;
    }
    month = (M0 - '0') * 10 + M1 - '0';
  }

  if (d0 == 0)
  {
    if (d1 < '0' || d1 > '9')
    {
      return BOOL::FALSE;
    }
    day = d1 - '0';
  }
  else
  {
    if (d0 < '0' || d0 > '9')
    {
      return BOOL::FALSE;
    }
    if (d1 < '0' || d1 > '9')
    {
      return BOOL::FALSE;
    }
    day = (d0 - '0') * 10 + d1 - '0';
  }

  if (month < 1)
  {
    return BOOL::FALSE;
  }

  if (day < 1)
  {
    return BOOL::FALSE;
  }

  switch (month)
  {
  case 1:
  case 3:
  case 5:
  case 7:
  case 8:
  case 10:
  case 12:
    if (day > 31)
    {
      return BOOL::FALSE;
    }
    break;
  case 2:
    if (day > 29)
    {
      return BOOL::FALSE;
    }
    break;
  case 4:
  case 6:
  case 9:
  case 11:
    if (day > 30)
    {
      return BOOL::FALSE;
    }
    break;
  default:
    break;
  }

  int index = len - 1;
  if (str->at(index) == '0' && str->at(index - 1) == '.')
  {
    index -= 2;
  }
  char x0 = str->at(index--);
  char x1 = str->at(index--);
  char x2 = str->at(index--);
  char x3 = str->at(index--);
  char x4 = str->at(index--);
  char x5 = str->at(index--);
  char x6 = str->at(index--);
  char x7 = str->at(index--);
  char x8 = str->at(index--);
  char x9 = str->at(index--);
  char x10 = str->at(index--);
  char x11 = str->at(index--);
  char x12 = str->at(index--);

  char h0, h1, m0, m1, s0, s1;

  if (x5 == ' ')
  {
    if (x1 != ':' || x3 != ':')
    {
      return BOOL::FALSE;
    }
    s0 = 0;
    s1 = x0;
    m0 = 0;
    m1 = x2;
    h0 = 0;
    h1 = x4;
  }
  else if (x6 == ' ')
  {
    if (x1 == ':')
    {
      s0 = 0;
      s1 = x0;

      if (x3 == ':')
      {
        m0 = 0;
        m1 = x2;
        h1 = x4;
        h0 = x5;
      }
      else if (x4 == ':')
      {
        m0 = x2;
        m1 = x3;
        h0 = 0;
        h1 = x5;
      }
      else
      {
        return BOOL::FALSE;
      }
    }
    else if (x2 == ':')
    {
      s0 = x0;
      s1 = x1;

      if (x4 != ':')
      {
        return BOOL::FALSE;
      }

      m0 = 0;
      m1 = x3;
      h0 = 0;
      h1 = x5;
    }
    else
    {
      return BOOL::FALSE;
    }
  }
  else if (x7 == ' ')
  {
    if (x1 == ':')
    {
      s0 = 0;
      s1 = x0;

      if (x4 != ':')
      {
        return BOOL::FALSE;
      }
      m1 = x2;
      m0 = x3;
      h1 = x5;
      h0 = x6;
    }
    else if (x2 == ':')
    {
      s0 = x0;
      s1 = x1;

      if (x4 == ':')
      {
        m0 = 0;
        m1 = x3;
        h1 = x5;
        h0 = x6;
      }
      else if (x5 == ':')
      {
        m1 = x3;
        m0 = x4;
        h0 = 0;
        h1 = x6;
      }
      else
      {
        return BOOL::FALSE;
      }
    }
    else
    {
      return BOOL::FALSE;
    }
  }
  else if (x8 == ' ')
  {
    if (x2 != ':' || x5 != ':')
    {
      return BOOL::FALSE;
    }
    s1 = x0;
    s0 = x1;
    m1 = x3;
    m0 = x4;
    h1 = x6;
    h0 = x7;
  }
  else if (x12 == ' ')
  {
    if (x3 != '.' || x6 != ':' || x9 != ':')
    {
      return BOOL::FALSE;
    }
    s1 = x4;
    s0 = x5;
    m1 = x7;
    m0 = x8;
    h1 = x10;
    h0 = x11;

    int S2 = x0;
    int S1 = x1;
    int S0 = x2;

    if (S0 < '0' || S0 > '9')
    {
      return BOOL::FALSE;
    }

    if (S1 < '0' || S1 > '9')
    {
      return BOOL::FALSE;
    }

    if (S2 < '0' || S2 > '9')
    {
      return BOOL::FALSE;
    }
  }
  else
  {
    return BOOL::FALSE;
  }

  if (h0 == 0)
  {
    if (h1 < '0' || h1 > '9')
    {
      return BOOL::FALSE;
    }
  }
  else
  {
    if (h0 < '0' || h0 > '2')
    {
      return BOOL::FALSE;
    }
    if (h1 < '0' || h1 > '9')
    {
      return BOOL::FALSE;
    }

    int hour = (h0 - '0') * 10 + (h1 - '0');
    if (hour > 24)
    {
      return BOOL::FALSE;
    }
  }

  if (m0 == 0)
  {
    if (m1 < '0' || m1 > '9')
    {
      return BOOL::FALSE;
    }
  }
  else
  {
    if (m0 < '0' || m0 > '6')
    {
      return BOOL::FALSE;
    }
    if (m1 < '0' || m1 > '9')
    {
      return BOOL::FALSE;
    }

    int minute = (m0 - '0') * 10 + (m1 - '0');
    if (minute > 60)
    {
      return BOOL::FALSE;
    }
  }

  if (s0 == 0)
  {
    if (s1 < '0' || s1 > '9')
    {
      return BOOL::FALSE;
    }
  }
  else
  {
    if (s0 < '0' || s0 > '6')
    {
      return BOOL::FALSE;
    }
    if (s1 < '0' || s1 > '9')
    {
      return BOOL::FALSE;
    }

    int second = (s0 - '0') * 10 + (s1 - '0');
    if (second > 60)
    {
      return BOOL::FALSE;
    }
  }

  return BOOL::TRUE;
}

std::shared_ptr<SQLTimestampExpr> SQLTimestampExpr::of(string_ptr str)
{
  return std::shared_ptr<SQLTimestampExpr>(new SQLTimestampExpr(str));
}
